﻿using ServerListerner.Helper;

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace ServerListerner.Connections
{
    internal class UDPConnection : IUDPConnection
    {
        private bool IsClosed = false;
        private Socket _socket;
        private int bufferSize = 0;
        private int sendBufferSize = 0;
        private byte[] _buffer;
        private List<byte> _recBuffer;
        private EndPoint _remoteEndPoint;
        public Action<IUDPConnection, byte[]> OnReceived { get; set; }
        public Action<IUDPConnection, string, Exception> OnError { get; set; }

        public UDPConnection(Socket socket, Action<IUDPConnection>? action) {
            this._socket = socket;
            this.bufferSize = this._socket.ReceiveBufferSize;
            this.sendBufferSize = this._socket.SendBufferSize;
            action?.Invoke(this);
            _=Task.Run(StartReceived);
        }
        ~UDPConnection()
        {
            GC.Collect();
        }
        private void StartReceived()
        {
            try
            {
                while (!this.IsClosed)
            {
                    _remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
                    this._buffer = null;
                    this._buffer = new byte[this.bufferSize];
                    var iar = this._socket.BeginReceiveFrom(this._buffer, 0, this.bufferSize, SocketFlags.None, ref _remoteEndPoint, null, null);
                    if (this._recBuffer == null)
                        this._recBuffer = new List<byte>();
                    int bytesRead = this._socket.EndReceiveFrom(iar, ref _remoteEndPoint);
                    this._recBuffer.AddRange(this._buffer.Take(bytesRead));
                    Task.Delay(1).Wait();
                    if (this._socket.Available <= 0)
                        if (this._recBuffer != null && this._recBuffer.Count > 0)
                        {
                            OnReceived?.Invoke(this, this._recBuffer.ToArray());
                            this._recBuffer.Clear();
                        }
                }
            }
            catch (Exception ex)
            {
                OnError?.Invoke(this, "UDPStartReceived Error", ex);
            }

        }
        //private void Received(IAsyncResult iar)
        //{
        //    try
        //    {
        //        if (this._socket == null) return;
        //        _remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
        //        int bytesRead = this._socket.EndReceiveFrom(iar, ref _remoteEndPoint);
        //        if (this._recBuffer == null)
        //            this._recBuffer = new List<byte>();
        //        this._recBuffer.AddRange(this._buffer.Take(bytesRead));
        //        Task.Delay(1).Wait();
        //        if (this._socket.Available<=0)
        //           if(this._recBuffer!=null&&this._recBuffer.Count>0)
        //            {
        //                OnReceived?.Invoke(this, this._recBuffer.ToArray());
        //                this._recBuffer.Clear();
        //            }
        //    }
        //    catch (Exception ex)
        //    {
        //        OnError?.Invoke(this, "UDPReceived Error", ex);
        //    }
        //    StartReceived();
        //}
        public void Send(EndPoint point, byte[] bytes)
        {
            try
            {
                while (bytes.Length>0)
                {
                    var send = bytes.Take(this.sendBufferSize).ToArray();
                    int len = send.Length > this.sendBufferSize ? this.sendBufferSize : send.Length;
                    bytes = bytes.Skip(len).ToArray();
                    var iar = this._socket.BeginSendTo(send, 0, len, SocketFlags.None, point, null, null) ;
                    this._socket.EndSendTo(iar);
                }
            }
            catch (Exception ex)
            {
                OnError?.Invoke(this, "UDPSend Error", ex);
            }
            
        }
        public void Send(IPAddress iP, int port, byte[] data) => this.Send(new IPEndPoint(iP, port), data);
        public void Send(string ip, int port, byte[] data) => this.Send(IPAddress.Parse(ip), port, data);
        public EndPoint RemoteEndPoint { get =>_remoteEndPoint; }
        public void Dispose()
        {
           this.Close();
        }
        private void Close()
        {
            this.IsClosed = true;
            this.CloseSocket();
            this.SetNull();
            GC.Collect();
        }
        private void CloseSocket()
        {
            if(this._socket.Connected)
            this._socket.Shutdown(SocketShutdown.Both);
            this._socket.Close();
            this._socket.Dispose();
        }
        private void SetNull()
        {
            this._buffer = null;
            this._socket = null;
            OnReceived = null;
            OnError = null;
        }
    }
}
